package com.oig.sys.workflow.flow.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.lark.oapi.Client;
import com.lark.oapi.core.request.EventReq;
import com.lark.oapi.core.request.FormData;
import com.lark.oapi.core.response.RawResponse;
import com.lark.oapi.core.token.AccessTokenType;
import com.lark.oapi.event.CustomEventHandler;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.service.approval.v4.model.CancelInstanceReq;
import com.lark.oapi.service.approval.v4.model.CancelInstanceResp;
import com.lark.oapi.service.approval.v4.model.CreateInstanceReq;
import com.lark.oapi.service.approval.v4.model.CreateInstanceResp;
import com.lark.oapi.service.approval.v4.model.GetApprovalReq;
import com.lark.oapi.service.approval.v4.model.GetApprovalResp;
import com.lark.oapi.service.approval.v4.model.GetInstanceReq;
import com.lark.oapi.service.approval.v4.model.GetInstanceResp;
import com.lark.oapi.service.approval.v4.model.GetInstanceRespBody;
import com.lark.oapi.service.approval.v4.model.InstanceCancel;
import com.lark.oapi.service.approval.v4.model.InstanceCcUser;
import com.lark.oapi.service.approval.v4.model.InstanceCreate;
import com.lark.oapi.service.approval.v4.model.SubscribeApprovalReq;
import com.lark.oapi.service.approval.v4.model.SubscribeApprovalResp;
import com.lark.oapi.service.approval.v4.model.UnsubscribeApprovalReq;
import com.lark.oapi.service.approval.v4.model.UnsubscribeApprovalResp;
import com.lark.oapi.service.im.ImService;
import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;
import com.oig.common.exception.BadRequestException;
import com.oig.common.exception.ServiceException;
import com.oig.common.util.FileUtil;
import com.oig.common.util.FlexibleDateUtil;
import com.oig.common.util.JsonUtil;
import com.oig.common.util.SpringContextUtil;
import com.oig.sys.workflow.constants.FlowEnums;
import com.oig.sys.workflow.flow.IDepFlowService;
import com.oig.sys.workflow.flow.event.ApprovalStatusChangeEvent;
import com.oig.sys.workflow.flow.module.FeishuAmtFormModule;
import com.oig.sys.workflow.flow.module.FeishuFormModule;
import com.oig.sys.workflow.vo.InstanceAllInfoVo;
import com.oig.sys.workflow.flow.module.FormAttachment;
import com.oig.sys.workflow.flow.module.FormOption;
import com.oig.sys.workflow.vo.FlowConfigVo;
import com.oig.sys.workflow.vo.FlowFormVo;
import com.oig.sys.workflow.vo.FlowInstanceVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
@Slf4j
@RequiredArgsConstructor
public class DepFlowServiceImpl implements IDepFlowService {

    @Value("${sys.feishu.app-id}")
    private String appId;
    @Value("${sys.feishu.app-secret}")
    private String appSecret;

    private final static String format = "yyyy-MM-dd'T'HH:mm:ss+08:00";

    /**
     * 查询飞书审批流定义
     */
    public FlowConfigVo queryFlowDefine(String flowCode) {
        Client client = Client.newBuilder(appId, appSecret).build();
        // 创建请求对象
        GetApprovalReq req = GetApprovalReq.newBuilder()
                .approvalCode(flowCode)
                .withAdminId(false)
                .build();
        GetApprovalResp resp ;
        try {
            // 发起请求
            resp = client.approval().v4().approval().get(req);
            log.debug("fei shu 返回值：{}", JsonUtil.toJSONString(resp));
        } catch (Exception e){
            throw new ServiceException(e.getMessage());
        }
        if (!resp.success()) {
            log.warn("未查到数据");
            throw new ServiceException(resp.getMsg());
        }
        FlowConfigVo flowConfig = new FlowConfigVo();
        flowConfig.setFlowCode(flowCode);
        flowConfig.setFlowName(resp.getData().getApprovalName());
        flowConfig.setFormJson(resp.getData().getForm());
        JSONArray jsonArray = JSONUtil.parseArray(resp.getData().getForm());
        flowConfig.setFlowFormVoList(this.parseForm(jsonArray));
//              flowConfig.setNodeJson(resp.getData().getNodeList());
        return flowConfig;
    }

    private List<FlowFormVo> parseForm(JSONArray jsonArray){
        List<FlowFormVo> flowFormVoList = new ArrayList<>();
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject obj = jsonArray.getJSONObject(i);
            FlowFormVo formVo = new FlowFormVo();
            formVo.setFieldId(obj.getStr("custom_id"));
            formVo.setFieldName(obj.getStr("name"));
            formVo.setFieldType(obj.getStr("type"));
            formVo.setRequired(obj.getStr("required"));
            if (obj.getBool("enable_default_value")) {
                formVo.setDefaultValue(obj.getStr("widget_default_value"));
            }
            formVo.setFieldOption(obj.getStr("option"));
            if (FlowEnums.FormType.FIELD_LIST.getCode().equals(formVo.getFieldType())){
                formVo.setChildren(this.parseForm(obj.getJSONArray("children")));
            }
            flowFormVoList.add(formVo);
        }
        return flowFormVoList;
    }


    @Override
    public boolean subscribe(String flowCode) {
        Client client = Client.newBuilder(appId, appSecret).build();
        // 创建请求对象
        SubscribeApprovalReq req = SubscribeApprovalReq.newBuilder()
                .approvalCode(flowCode)
                .build();
        try {
            // 发起请求
            SubscribeApprovalResp resp = client.approval().v4().approval().subscribe(req);
            log.debug("开启订阅飞书审批状态变更，接口返回值：{}", JsonUtil.toJSONString(resp));
            if (resp.success()) {
                return true;
            }
        } catch (Exception e){
            log.error("订阅失败", e);
        }
        return false;
    }

    @Override
    public boolean unsubscribe(String flowCode) {
        Client client = Client.newBuilder(appId, appSecret).build();
        // 创建请求对象
        UnsubscribeApprovalReq req = UnsubscribeApprovalReq.newBuilder()
                .approvalCode(flowCode)
                .build();
        try {
            // 发起请求
            UnsubscribeApprovalResp resp = client.approval().v4().approval().unsubscribe(req);
            log.debug("取消审批状态变更通知订阅飞书，接口返回值：{}", JsonUtil.toJSONString(resp));
            if (resp.success()) {
                return true;
            }
        } catch (Exception e){
            log.error("取消订阅失败", e);
        }
        return false;
    }

    /**
     * 飞书返回值
     * {
     *     "code": 0,
     *     "msg": "success",
     *     "data": {
     *         "instance_code": "81D31358-93AF-92D6-7425-01A5D67C4E71"
     *     }
     * }
     */
    @Override
    public String createInstance(FlowConfigVo flowConfigVo, FlowInstanceVo flowInstanceVo) throws Exception {
        Map<String,Object> applyData = JsonUtil.stringToCollect(flowInstanceVo.getFlowData());
        List<FeishuFormModule> formList = this.dealFormValue(flowConfigVo.getFlowFormVoList(), applyData);
        log.debug("form data:{}",JsonUtil.toJSONString(formList));
        //请求飞书
        Client client = Client.newBuilder(appId, appSecret).build();
        CreateInstanceResp rsp = client.approval().instance()
                    .create(CreateInstanceReq
                            .newBuilder()
                            .instanceCreate(InstanceCreate.newBuilder()
                                    .approvalCode(flowConfigVo.getFlowCode())
                                    .uuid(flowInstanceVo.getBizId())
                                    .userId(flowInstanceVo.getLastUpdateCode())
                                    .form(JsonUtil.toJSONString(formList))
                                    .allowResubmit(false)
                                    .allowSubmitAgain(true)
                                    .forbidRevoke(false)
                                    .build())
                            .build());
        log.debug("fs rtn:{}", JsonUtil.toJSONString(rsp));
        if (rsp.success()){
            return rsp.getData().getInstanceCode();
        } else {
            throw new ServiceException(rsp.getMsg());
        }
    }

    private List<FeishuFormModule> dealFormValue(List<FlowFormVo> flowFormVoList, Map<String, Object> applyData){
        List<FeishuFormModule> formList = new ArrayList<>() ;
        for (FlowFormVo formVo : flowFormVoList){
            FeishuFormModule feishuFormModule = new FeishuFormModule();
            feishuFormModule.setId(formVo.getFieldId());
            feishuFormModule.setType(formVo.getFieldType());
            feishuFormModule.setValue(formVo.getDefaultValue());
            Object value = applyData.get(formVo.getFieldId());
            if (value==null){
                if (feishuFormModule.getValue()!=null){
                    log.debug("use default value");
                    formList.add(feishuFormModule);
                } else if ("true".equals(formVo.getRequired())){
                    throw new BadRequestException(formVo.getFieldName()+"没有传值");
                }
                continue;
            }
            switch (formVo.getFieldType()){
                case "date":
                    log.debug("date");
                    feishuFormModule.setValue(this.formatDate(value));
                    break;
                case "dateInterval":
                    log.debug("dateInterval");
                    FeishuFormModule.DateInterval dateInterval = new FeishuFormModule.DateInterval();
                    dateInterval.setStart(this.formatDate(applyData.get("startTime")));
                    dateInterval.setEnd(this.formatDate(applyData.get("endTime")));
                    feishuFormModule.setValue(dateInterval);
                    break;
                case "amount":
                    log.debug("amount");
                    FeishuAmtFormModule amtFormModule = new FeishuAmtFormModule();
                    amtFormModule.setId(formVo.getFieldId());
                    amtFormModule.setType(formVo.getFieldType());
                    if (value instanceof CharSequence){ //传值兼容处理
                        amtFormModule.setValue(new BigDecimal(String.valueOf(value)));
                    } else {
                        amtFormModule.setValue(value);
                    }
                    amtFormModule.setCurrency(applyData.getOrDefault("currency", "CNY").toString());
                    feishuFormModule = amtFormModule;
                    break;
                case "telephone":
                    log.debug("telephone");
                    FeishuFormModule.TelephoneV telephone = new FeishuFormModule.TelephoneV();
                    telephone.setNationalNumber(String.valueOf(value));
                    feishuFormModule.setValue(telephone);
                    break;
                case "imageV2":
                case "attachmentV2":
                    log.debug("file");
                    String attArr = JsonUtil.toJSONString(value);
                    List<FormAttachment> attachmentList = JsonUtil.toList(attArr, FormAttachment.class);
                    log.debug("fileList :{}", attachmentList);
                    feishuFormModule.setValue(this.uploadAttachment(attachmentList));
                    break;
                case "radioV2":
                    log.debug("radio");
                    if (StringUtils.isNotBlank(formVo.getFieldOption())){
                        //单选用飞书的备选值，传名称过来转成飞书值
                        List<FormOption> formOptionList = JsonUtil.toList(formVo.getFieldOption(), FormOption.class);
                        if (formOptionList.isEmpty()){
                            feishuFormModule.setValue(value);
                            break;
                        }
                        Map<String,String> tv = formOptionList.stream().collect(Collectors.toMap(FormOption::getText,FormOption::getValue));
                        String txtToValue = tv.get(String.valueOf(value));
                        if (txtToValue==null){
                            throw new ServiceException(formVo.getFieldName()+"找不到选项") ;
                        }
                        feishuFormModule.setValue(txtToValue);
                    } else {
                        feishuFormModule.setValue(value);
                    }
                    break;
                case "checkboxV2":
                    log.debug("checkbox");
                    if (StringUtils.isNotBlank(formVo.getFieldOption())){
                        //单选用飞书的备选值，传名称过来转成飞书值
                        List<FormOption> formOptionList = JsonUtil.toList(formVo.getFieldOption(), FormOption.class);
                        if (formOptionList.isEmpty()){
                            log.debug("no options");
                            feishuFormModule.setValue(value);
                            break;
                        }
                        Map<String,String> tv = formOptionList.stream().collect(Collectors.toMap(FormOption::getText,FormOption::getValue));
                        List<String> fvList = new ArrayList<>();
                        String arr = JSONUtil.toJsonStr(value);
                        List<String> itValList = JsonUtil.toList(arr, String.class);
                        for (String item : itValList){
                            String txtToValue = tv.get(item);
                            if (txtToValue==null){
                                throw new ServiceException(formVo.getFieldName()+"找不到选项："+item) ;
                            }
                            fvList.add(txtToValue);
                        }
                        feishuFormModule.setValue(fvList);
                    } else {
                        feishuFormModule.setValue(value);
                    }
                    break;
                case "fieldList":
                    Map<String,Object> childrenDataMap = JsonUtil.stringToCollect(String.valueOf(value));
                    feishuFormModule.setValue(Collections.singletonList(this.dealFormValue(formVo.getChildren(),childrenDataMap)));
                    break;
                case "contact":
                    if (value instanceof CharSequence){ ////传值兼容处理 如果是字符串，那么转成list
                        List<String> itValList = JsonUtil.toList(String.valueOf(value), String.class);
                        feishuFormModule.setValue(itValList);
                    } else {
                        feishuFormModule.setValue(value);
                    }
                    break;
                default:
                    feishuFormModule.setValue(value);
            }
            formList.add(feishuFormModule);
        }
        return formList;
    }

    private String formatDate(Object value){
        if (value instanceof LocalDateTime) {
            LocalDateTime dateTime = (LocalDateTime)value;
            return LocalDateTimeUtil.format(dateTime, format);
        } else if (value instanceof LocalDate) {
            LocalDate dateTime = (LocalDate)value;
            return LocalDateTimeUtil.format(dateTime, format);
        } else if (value instanceof Date) {
            Date dateTime = (Date)value;
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
            return simpleDateFormat.format(dateTime);
        } else if (value instanceof String) {
            Date dateTime = FlexibleDateUtil.parse((String)value) ;
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
            return simpleDateFormat.format(dateTime);
        }
        return null;
    }


    private List<String> uploadAttachment(List<FormAttachment> attachmentList) {
        List<String> fileIdList = new ArrayList<>();
        Client client = Client.newBuilder(appId, appSecret).build();
        attachmentList.forEach(item->{
            FormData formData = new FormData();
            formData.addField("name", item.getFileName());
            formData.addField("type", "attachment");
            formData.addFile("content", this.downloadFile(item.getFileName(), item.getFileUrl()));
            try {
                RawResponse rawResponse = client.post("https://www.feishu.cn/approval/openapi/v2/file/upload", formData, AccessTokenType.Tenant);
                if (rawResponse.getStatusCode()==200){
                    String bodyStr = new String(rawResponse.getBody());
                    JSONObject jsonObject = JSONUtil.parseObj(bodyStr);
                    if (jsonObject.getInt("code")==0){
                        JSONObject data = jsonObject.getJSONObject("data");
                        fileIdList.add(data.getStr("code"));
                    }
                }
            } catch (Exception e) {
                log.error("附件上传失败",e);
                throw new RuntimeException(e);
            }
        });
        return fileIdList;
    }

    private File downloadFile(String fileName,String url) {
        String suffix = null;
        if (fileName.contains(".")){    //优先以文件名的后缀创建临时文件
            suffix = fileName.substring(fileName.lastIndexOf("."));
            log.debug("fileName:{} sub --{}",fileName, suffix);
        } else {
            String subUrl ;
            if (url.contains("?")){
                subUrl = url.substring(0, url.indexOf("?"));
                suffix = subUrl.substring(subUrl.lastIndexOf("."));
                log.debug("subUrl:{}---suffix:{}", subUrl, suffix);
            }
        }
        if (suffix==null){
            throw new BadRequestException("获取不到文件后缀");
        }
        try {
            File tempFile = File.createTempFile("temp"+System.currentTimeMillis(), suffix);
            //HttpUtil.downloadFile(url,tempFile)
            //byte[] bytes = HttpDownloader.downloadBytes(url) ;
            //IoUtil.write(Files.newOutputStream(tempFile.toPath()), true, bytes);
            return FileUtil.downloadByUrl(url,tempFile);
        } catch (Exception e){
            throw new ServiceException("临时下载文件出错");
        }
    }


    @Override
    public void cancelInstance(String flowCode, String instanceId, String userId) throws Exception {
        log.debug("approvalCode:{}--instance:{}--userId:{}",flowCode, instanceId, userId);
        Client client = Client.newBuilder(appId, appSecret).build();
        CancelInstanceResp rsp = client.approval().instance()
                .cancel(CancelInstanceReq.newBuilder()
                        .userIdType("user_id")
                        .instanceCancel(InstanceCancel.newBuilder()
                                .approvalCode(flowCode)
                                .instanceCode(instanceId)
                                .userId(userId)
                                .build())
                        .build());
        log.debug("fs cancel rtn:{}", JsonUtil.toJSONString(rsp));
        if (!rsp.success()){
            throw new ServiceException(rsp.getMsg());
        }
    }


    @Override
    public InstanceAllInfoVo getInstance(String instanceId, String userId) {
        Client client = Client.newBuilder(appId, appSecret).build();
        GetInstanceReq req = GetInstanceReq.newBuilder()
                .locale("zh-CN")
                .userId(userId)
                .userIdType("user_id")
                .instanceId(instanceId)
                .build();
        GetInstanceResp resp;
        try {
            resp = client.approval().instance().get(req);
            log.debug("fei shu 返回值：{}", JsonUtil.toJSONString(resp));
        } catch (Exception e){
            throw new ServiceException(e.getMessage());
        }
        if (!resp.success()) {
            log.warn("未查到数据");
            throw new ServiceException(resp.getMsg());
        }
        return this.buildInstance(resp.getData());
    }

    private InstanceAllInfoVo buildInstance(GetInstanceRespBody respBody){
        InstanceAllInfoVo instance = new InstanceAllInfoVo();
        instance.setInstanceCode(respBody.getInstanceCode());
        instance.setApprovalCode(respBody.getApprovalCode());
        instance.setApprovalName(respBody.getApprovalName());
        instance.setUserId(respBody.getUserId());
        instance.setStatus(respBody.getStatus());
        instance.setStartTime(respBody.getStartTime());
        instance.setEndTime(respBody.getEndTime());
        instance.setDetailRaw(respBody.getForm());
        instance.setSerialNumber(respBody.getSerialNumber());
        instance.setReverted(respBody.getReverted());
        instance.setTaskList(this.toTaskList(respBody));
        instance.setTimeline(this.toTimeline(respBody));
        return instance;
    }

    private List<InstanceAllInfoVo.InstanceTask> toTaskList(GetInstanceRespBody respBody){
        List<InstanceAllInfoVo.InstanceTask> taskList = new ArrayList<>();
        Arrays.stream(respBody.getTaskList()).forEach(instanceTask -> {
            InstanceAllInfoVo.InstanceTask task = new InstanceAllInfoVo.InstanceTask();
            task.setId(instanceTask.getId());
            task.setNodeId(instanceTask.getNodeId());
            task.setNodeName(instanceTask.getNodeName());
            task.setCustomNodeId(instanceTask.getCustomNodeId());
            task.setType(instanceTask.getType());
            task.setStatus(instanceTask.getStatus());
            task.setStartTime(instanceTask.getStartTime());
            task.setEndTime(instanceTask.getEndTime());
            taskList.add(task);
        });
        return taskList;
    }

    private List<InstanceAllInfoVo.InstanceTimeline> toTimeline(GetInstanceRespBody respBody){
        List<InstanceAllInfoVo.InstanceTimeline> timelineList = new ArrayList<>();
        Arrays.stream(respBody.getTimeline()).forEach(item->{
            InstanceAllInfoVo.InstanceTimeline timeline = new InstanceAllInfoVo.InstanceTimeline();
            timeline.setType(item.getType());
            timeline.setNodeKey(item.getNodeKey());
            timeline.setTaskId(item.getTaskId());
            timeline.setComments(item.getComment());
            timeline.setUserId(item.getUserId());
            timeline.setCreateTime(item.getCreateTime());
            if (item.getFiles()!=null && item.getFiles().length>0){
                timeline.setFileList(BeanUtil.copyToList(Arrays.asList(item.getFiles()),InstanceAllInfoVo.InstanceFile.class));
            }
            if (item.getCcUserList()!=null && item.getCcUserList().length>0){
                timeline.setCcUserList(Arrays.stream(item.getCcUserList()).map(InstanceCcUser::getUserId).collect(Collectors.toList()));
            }
            timelineList.add(timeline);
        });
        return timelineList;
    }

    @Override
    public void startListener() {
        com.lark.oapi.ws.Client client = new com.lark.oapi.ws.Client.Builder(appId, appSecret)
                .eventHandler(EventDispatcher.newBuilder("", "")
                        .onP2MessageReceiveV1(new ImService.P2MessageReceiveV1Handler() {
                            @Override
                            public void handle(P2MessageReceiveV1 event) throws Exception {
                                System.out.println(JsonUtil.toJSONString(event.getEvent()));
                            }
                        })
                        .onCustomizedEvent("approval_instance", new CustomEventHandler() {
                            @Override
                            public void handle(EventReq event) throws Exception {
                                //1.0版本事件
                                System.out.printf("approval_instance, data: %s\n", new String(event.getBody(), StandardCharsets.UTF_8));
                                SpringContextUtil.publishEvent(new ApprovalStatusChangeEvent(new String(event.getBody(), StandardCharsets.UTF_8)));
                            }
                        })
                        .build())
                .build();
        client.start();
    }

}
